baubs git
Commit e07d6b9a6f3fbefdfc93a925490088b88e8c470f
Parents : a6d9d26
Author : Jan-Henrik Bruhn <jan-henrik.bruhn@offis.de>
Date : 2025-12-17T00:23:56+01:00
refactor: Extract PatternInfo component to eliminate duplication
Created shared PatternInfo component for displaying pattern statistics
(size, stitch count, colors) used in both FileUpload and PatternSummaryCard.
Reduces code duplication and ensures consistency across the UI.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Changes
3 files changed, 96 insertions(+), 138 deletions(-)
Diff
diff --git a/src/components/FileUpload.tsx b/src/components/FileUpload.tsx
index f83a912..201166a 100644
--- a/src/components/FileUpload.tsx
+++ b/src/components/FileUpload.tsx
@@ -6,6 +6,7 @@ import { useUIStore } from '../stores/useUIStore';
import { convertPesToPen, type PesPatternData } from '../formats/import/pesImporter';
import { canUploadPattern, getMachineStateCategory } from '../utils/machineStateHelpers';
import { PatternInfoSkeleton } from './SkeletonLoader';
+import { PatternInfo } from './PatternInfo';
import { ArrowUpTrayIcon, CheckCircleIcon, DocumentTextIcon, FolderOpenIcon } from '@heroicons/react/24/solid';
import { createFileService } from '../platform';
import type { IFileService } from '../platform/interfaces/IFileService';
@@ -200,74 +201,7 @@ export function FileUpload() {
{!isLoading && pesData && (
<div className="mb-3">
- <div className="grid grid-cols-3 gap-2 text-xs mb-2">
- <div className="bg-gray-50 dark:bg-gray-700/50 p-2 rounded">
- <span className="text-gray-600 dark:text-gray-400 block">Size</span>
- <span className="font-semibold text-gray-900 dark:text-gray-100">
- {((pesData.bounds.maxX - pesData.bounds.minX) / 10).toFixed(1)} x{' '}
- {((pesData.bounds.maxY - pesData.bounds.minY) / 10).toFixed(1)} mm
- </span>
- </div>
- <div className="bg-gray-50 dark:bg-gray-700/50 p-2 rounded">
- <span className="text-gray-600 dark:text-gray-400 block">Stitches</span>
- <span className="font-semibold text-gray-900 dark:text-gray-100">
- {pesData.penStitches?.stitches.length.toLocaleString() || pesData.stitchCount.toLocaleString()}
- {pesData.penStitches && pesData.penStitches.stitches.length !== pesData.stitchCount && (
- <span
- className="text-gray-500 dark:text-gray-500 font-normal ml-1"
- title="Input stitch count from PES file (lock stitches were added for machine compatibility)"
- >
- ({pesData.stitchCount.toLocaleString()})
- </span>
- )}
- </span>
- </div>
- <div className="bg-gray-50 dark:bg-gray-700/50 p-2 rounded">
- <span className="text-gray-600 dark:text-gray-400 block">Colors / Blocks</span>
- <span className="font-semibold text-gray-900 dark:text-gray-100">
- {pesData.uniqueColors.length} / {pesData.threads.length}
- </span>
- </div>
- </div>
-
- <div className="flex items-center gap-2 mb-2">
- <span className="text-xs text-gray-600 dark:text-gray-400">Colors:</span>
- <div className="flex gap-1">
- {pesData.uniqueColors.slice(0, 8).map((color, idx) => {
- // Primary metadata: brand and catalog number
- const primaryMetadata = [
- color.brand,
- color.catalogNumber ? `#${color.catalogNumber}` : null
- ].filter(Boolean).join(" ");
-
- // Secondary metadata: chart and description
- const secondaryMetadata = [
- color.chart,
- color.description
- ].filter(Boolean).join(" ");
-
- const metadata = [primaryMetadata, secondaryMetadata].filter(Boolean).join(" • ");
-
- const tooltipText = metadata
- ? `Color ${idx + 1}: ${color.hex} - ${metadata}`
- : `Color ${idx + 1}: ${color.hex}`;
-
- return (
- <div
- key={idx}
- className="w-3 h-3 rounded-full border border-gray-300 dark:border-gray-600"
- style={{ backgroundColor: color.hex }}
- title={tooltipText}
- />
- );
- })}
- {pesData.uniqueColors.length > 8 && (
- <div className="w-3 h-3 rounded-full bg-gray-300 dark:bg-gray-600 border border-gray-400 dark:border-gray-500 flex items-center justify-center text-xs font-bold text-gray-600 dark:text-gray-300 leading-none">
- +{pesData.uniqueColors.length - 8}
- </div>
- )}
- </div>
- </div>
+ <PatternInfo pesData={pesData} showThreadBlocks />
</div>
)}
diff --git a/src/components/PatternInfo.tsx b/src/components/PatternInfo.tsx
new file mode 100644
index 0000000..efff798
--- /dev/null
+++ b/src/components/PatternInfo.tsx
@@ -0,0 +1,92 @@
+import type { PesPatternData } from '../formats/import/pesImporter';
+
+interface PatternInfoProps {
+ pesData: PesPatternData;
+ showThreadBlocks?: boolean;
+}
+
+export function PatternInfo({ pesData, showThreadBlocks = false }: PatternInfoProps) {
+ return (
+ <>
+ <div className="grid grid-cols-3 gap-2 text-xs mb-2">
+ <div className="bg-gray-50 dark:bg-gray-700/50 p-2 rounded">
+ <span className="text-gray-600 dark:text-gray-400 block">Size</span>
+ <span className="font-semibold text-gray-900 dark:text-gray-100">
+ {((pesData.bounds.maxX - pesData.bounds.minX) / 10).toFixed(1)} x{' '}
+ {((pesData.bounds.maxY - pesData.bounds.minY) / 10).toFixed(1)} mm
+ </span>
+ </div>
+ <div className="bg-gray-50 dark:bg-gray-700/50 p-2 rounded">
+ <span className="text-gray-600 dark:text-gray-400 block">Stitches</span>
+ <span className="font-semibold text-gray-900 dark:text-gray-100">
+ {pesData.penStitches?.stitches.length.toLocaleString() || pesData.stitchCount.toLocaleString()}
+ {pesData.penStitches && pesData.penStitches.stitches.length !== pesData.stitchCount && (
+ <span
+ className="text-gray-500 dark:text-gray-500 font-normal ml-1"
+ title="Input stitch count from PES file (lock stitches were added for machine compatibility)"
+ >
+ ({pesData.stitchCount.toLocaleString()})
+ </span>
+ )}
+ </span>
+ </div>
+ <div className="bg-gray-50 dark:bg-gray-700/50 p-2 rounded">
+ <span className="text-gray-600 dark:text-gray-400 block">
+ {showThreadBlocks ? 'Colors / Blocks' : 'Colors'}
+ </span>
+ <span className="font-semibold text-gray-900 dark:text-gray-100">
+ {showThreadBlocks
+ ? `${pesData.uniqueColors.length} / ${pesData.threads.length}`
+ : pesData.uniqueColors.length
+ }
+ </span>
+ </div>
+ </div>
+
+ <div className="flex items-center gap-2 mb-2">
+ <span className="text-xs text-gray-600 dark:text-gray-400">Colors:</span>
+ <div className="flex gap-1">
+ {pesData.uniqueColors.slice(0, 8).map((color, idx) => {
+ // Primary metadata: brand and catalog number
+ const primaryMetadata = [
+ color.brand,
+ color.catalogNumber ? `#${color.catalogNumber}` : null
+ ].filter(Boolean).join(" ");
+
+ // Secondary metadata: chart and description
+ const secondaryMetadata = [
+ color.chart,
+ color.description
+ ].filter(Boolean).join(" ");
+
+ const metadata = [primaryMetadata, secondaryMetadata].filter(Boolean).join(" • ");
+
+ // Show which thread blocks use this color in PatternSummaryCard
+ const threadNumbers = color.threadIndices.map(i => i + 1).join(", ");
+ const tooltipText = showThreadBlocks
+ ? (metadata
+ ? `Color ${idx + 1}: ${color.hex} - ${metadata}`
+ : `Color ${idx + 1}: ${color.hex}`)
+ : (metadata
+ ? `Color ${idx + 1}: ${color.hex}\n${metadata}\nUsed in thread blocks: ${threadNumbers}`
+ : `Color ${idx + 1}: ${color.hex}\nUsed in thread blocks: ${threadNumbers}`);
+
+ return (
+ <div
+ key={idx}
+ className="w-3 h-3 rounded-full border border-gray-300 dark:border-gray-600"
+ style={{ backgroundColor: color.hex }}
+ title={tooltipText}
+ />
+ );
+ })}
+ {pesData.uniqueColors.length > 8 && (
+ <div className="w-3 h-3 rounded-full bg-gray-300 dark:bg-gray-600 border border-gray-400 dark:border-gray-500 flex items-center justify-center text-xs font-bold text-gray-600 dark:text-gray-300 leading-none">
+ +{pesData.uniqueColors.length - 8}
+ </div>
+ )}
+ </div>
+ </div>
+ </>
+ );
+}
diff --git a/src/components/PatternSummaryCard.tsx b/src/components/PatternSummaryCard.tsx
index 8a62357..a094f35 100644
--- a/src/components/PatternSummaryCard.tsx
+++ b/src/components/PatternSummaryCard.tsx
@@ -2,6 +2,7 @@ import { useShallow } from 'zustand/react/shallow';
import { useMachineStore } from '../stores/useMachineStore';
import { usePatternStore } from '../stores/usePatternStore';
import { canDeletePattern } from '../utils/machineStateHelpers';
+import { PatternInfo } from './PatternInfo';
import { DocumentTextIcon, TrashIcon } from '@heroicons/react/24/solid';
export function PatternSummaryCard() {
@@ -44,76 +45,7 @@ export function PatternSummaryCard() {
</div>
</div>
- <div className="grid grid-cols-3 gap-2 text-xs mb-3">
- <div className="bg-gray-50 dark:bg-gray-700/50 p-2 rounded">
- <span className="text-gray-600 dark:text-gray-400 block">Size</span>
- <span className="font-semibold text-gray-900 dark:text-gray-100">
- {((pesData.bounds.maxX - pesData.bounds.minX) / 10).toFixed(1)} x{' '}
- {((pesData.bounds.maxY - pesData.bounds.minY) / 10).toFixed(1)} mm
- </span>
- </div>
- <div className="bg-gray-50 dark:bg-gray-700/50 p-2 rounded">
- <span className="text-gray-600 dark:text-gray-400 block">Stitches</span>
- <span className="font-semibold text-gray-900 dark:text-gray-100">
- {pesData.penStitches?.stitches.length.toLocaleString() || pesData.stitchCount.toLocaleString()}
- {pesData.penStitches && pesData.penStitches.stitches.length !== pesData.stitchCount && (
- <span
- className="text-gray-500 dark:text-gray-500 font-normal ml-1"
- title="Input stitch count from PES file (lock stitches were added for machine compatibility)"
- >
- ({pesData.stitchCount.toLocaleString()})
- </span>
- )}
- </span>
- </div>
- <div className="bg-gray-50 dark:bg-gray-700/50 p-2 rounded">
- <span className="text-gray-600 dark:text-gray-400 block">Colors</span>
- <span className="font-semibold text-gray-900 dark:text-gray-100">
- {pesData.uniqueColors.length}
- </span>
- </div>
- </div>
-
- <div className="flex items-center gap-2 mb-2">
- <span className="text-xs text-gray-600 dark:text-gray-400">Colors:</span>
- <div className="flex gap-1">
- {pesData.uniqueColors.slice(0, 8).map((color, idx) => {
- // Primary metadata: brand and catalog number
- const primaryMetadata = [
- color.brand,
- color.catalogNumber ? `#${color.catalogNumber}` : null
- ].filter(Boolean).join(" ");
-
- // Secondary metadata: chart and description
- const secondaryMetadata = [
- color.chart,
- color.description
- ].filter(Boolean).join(" ");
-
- const metadata = [primaryMetadata, secondaryMetadata].filter(Boolean).join(" • ");
-
- // Show which thread blocks use this color
- const threadNumbers = color.threadIndices.map(i => i + 1).join(", ");
- const tooltipText = metadata
- ? `Color ${idx + 1}: ${color.hex}\n${metadata}\nUsed in thread blocks: ${threadNumbers}`
- : `Color ${idx + 1}: ${color.hex}\nUsed in thread blocks: ${threadNumbers}`;
-
- return (
- <div
- key={idx}
- className="w-3 h-3 rounded-full border border-gray-300 dark:border-gray-600"
- style={{ backgroundColor: color.hex }}
- title={tooltipText}
- />
- );
- })}
- {pesData.uniqueColors.length > 8 && (
- <div className="w-3 h-3 rounded-full bg-gray-300 dark:bg-gray-600 border border-gray-400 dark:border-gray-500 flex items-center justify-center text-xs font-bold text-gray-600 dark:text-gray-300 leading-none">
- +{pesData.uniqueColors.length - 8}
- </div>
- )}
- </div>
- </div>
+ <PatternInfo pesData={pesData} />
{canDelete && (
<button
Served by rngit 1.4.2 - Generated in 0.05s